package common

import (
	"encoding/json"
	"fmt"
	"github.com/cihub/seelog"
	"os"
	"path/filepath"
	"regexp"
	"strconv"
	"strings"
)

const (
	MaxRetryCount     = 20 // client attribute
	StatRollFrequency = 2  // client attribute

	TypeChanged int64 = -1 // marks the given key type is change, e.g. from string to list

	// db type
	TypeDB           = 0 // db
	TypeCluster      = 1
	TypeAliyunProxy  = 2 // aliyun proxy
	TypeTencentProxy = 3 // tencent cloud proxy

	TypeMaster = "master"
	TypeSlave  = "slave"
	TypeAll    = "all"

	Splitter = ";"
)

var (
	BigKeyThreshold int64 = 5120
	Logger          seelog.LoggerInterface
)

func HandleLogLevel(logLevel string) (string, error) {
	// seelog library is disgusting
	switch logLevel {
	case "debug":
		return "debug,info,warn,error,critical", nil
	case "":
		fallthrough
	case "info":
		return "info,warn,error,critical", nil
	case "warn":
		return "warn,error,critical", nil
	case "error":
		return "error,critical", nil
	default:
		return "", fmt.Errorf("unknown log level[%v]", logLevel)
	}
}

func IndexOfStrings(array []string, str string) int {
	for i, v := range array {
		if v == str {
			return i
		}
	}
	return -1
}

func ContainsAny(array []string, str ...string) bool {
	for _, v := range array {
		for _, s := range str {
			if v == s {
				return true
			}
		}
	}
	return false
}

func IfString(condition bool, v1 string, v2 string) string {
	if condition {
		return v1
	} else {
		return v2
	}
}

func JoinNotBlank(sep string, values ...string) string {
	var result string
	for _, value := range values {
		if value == "" {
			continue
		}
		if result != "" {
			result += sep
		}
		result += value
	}
	return result
}

func AddPrefix(prefix string, value string) string {
	if value == "" || prefix == "" {
		return value
	}
	return prefix + value
}

func DeftStr(str string, deft string) string {
	if str = strings.TrimSpace(str); str == "" {
		return deft
	} else {
		return str
	}
}

func ToInt(str string, deft int) int {
	value, err := strconv.Atoi(str)
	if err != nil {
		return deft
	} else {
		return value
	}
}

func ToIntLimit(str string, deft int, min int, max int) int {
	if str == "" {
		return deft
	}
	value, err := strconv.Atoi(str)
	if err != nil {
		value = deft
	}
	if value < min {
		return min
	} else if value > max {
		return max
	} else {
		return value
	}
}

func RegSplit(str string, pattern string) []string {
	if str = strings.TrimSpace(str); str == "" {
		return []string{}
	}
	regex := regexp.MustCompile(pattern)
	return regex.Split(str, -1)
}

func ToArray(str string) []string {
	return RegSplit(str, "\\s*[;,|]+\\s*")
}

func Decode(t interface{}, values ...interface{}) interface{} {
	for i := 0; i < len(values)-1; i += 2 {
		if values[i] == t {
			return values[i+1]
		}
	}
	if len(values)%2 == 1 {
		return values[len(values)-1]
	}
	return t
}

func ToString(value interface{}) string {
	if value == nil {
		return ""
	}
	switch value.(type) {
	case float64:
		return strconv.FormatFloat(value.(float64), 'f', -1, 64)
	case float32:
		return strconv.FormatFloat(float64(value.(float32)), 'f', -1, 64)
	case int:
		return strconv.Itoa(value.(int))
	case uint:
		return strconv.Itoa(int(value.(uint)))
	case int8:
		return strconv.Itoa(int(value.(int8)))
	case uint8:
		return strconv.Itoa(int(value.(uint8)))
	case int16:
		return strconv.Itoa(int(value.(int16)))
	case uint16:
		return strconv.Itoa(int(value.(uint16)))
	case int32:
		return strconv.Itoa(int(value.(int32)))
	case uint32:
		return strconv.Itoa(int(value.(uint32)))
	case int64:
		return strconv.FormatInt(value.(int64), 10)
	case uint64:
		return strconv.FormatUint(value.(uint64), 10)
	case string:
		return value.(string)
	case []byte:
		return string(value.([]byte))
	default:
		newValue, _ := json.Marshal(value)
		return string(newValue)
	}
	return ""
}

func ScanPath(path string, handler func(*os.File) error) error {
	stat, err := os.Stat(path)
	if err != nil {
		return err
	}
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	if stat.IsDir() {
		err = doScanDir(path, file, handler)
	} else {
		err = handler(file)
		file.Close()
	}
	return err
}

func doScanDir(path string, file *os.File, handler func(*os.File) error) error {
	files, err := file.Readdir(-1)
	if err != nil {
		return err
	}
	for _, f := range files {
		file, err = os.Open(filepath.Join(path, f.Name()))
		if err != nil {
			return err
		}
		if f.IsDir() {
			err = doScanDir(filepath.Join(path, f.Name()), file, handler)
		} else {
			err = handler(file)
		}
		file.Close()
		if err != nil {
			return err
		}
	}
	return nil
}

func FileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}

func IsDir(path string) bool {
	stat, err := os.Stat(path)
	return err == nil && stat.IsDir()
}

func IsFile(path string) bool {
	stat, err := os.Stat(path)
	return err == nil && !stat.IsDir()
}

func ShowBytes(bytes uint64) string {
	if bytes < 1024 {
		return fmt.Sprintf("%dB", bytes)
	}
	remain := float64(bytes) / 1024
	if remain < 1024 {
		return fmt.Sprintf("%.3fK", remain)
	}
	remain = remain / 1024
	if remain < 1024 {
		return fmt.Sprintf("%.3fM", remain)
	}
	remain = remain / 1024
	return fmt.Sprintf("%.3fG", remain)
}

func AbsPath(path string) string {
	abs, err := filepath.Abs(path)
	if err != nil {
		return path
	} else {
		return abs
	}
}

func CreateFile(path string, name string, append bool) (*os.File, error) {
	if !strings.HasSuffix(path, "/") {
		path = path + "/"
	}
	path = filepath.Dir(path)
	err := os.MkdirAll(path, 777)
	if err != nil {
		return nil, nil
	}
	fileName := filepath.Join(path, name)
	if append {
		return os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	} else {
		return os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
	}
}

func FileName(name string) string {
	index := strings.LastIndex(name, ".")
	if index >= 0 {
		return name[0:index]
	} else {
		return name
	}
}
